🎖️GitЯра🎖️
Node / meshtastic / Meshtastic-Android / files / core / data / src / commonMain / kotlin / org / meshtastic / core / data / ai / AiFunctionProviderImpl.kt
Displaying Raw • Download
core/data/src/commonMain/kotlin/org/meshtastic/core/data/ai/AiFunctionProviderImpl.kt 3ca87fa0323214c7204c9d37acfe9d4b6ab6ea8b (3ca87fa0) Text, 24.40 KB
T8b949e/*
* Copyright (c) 2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
Tff7b72package T7ee787org.meshtastic.core.data.ai
Tff7b72import T7ee787kotlinx.coroutines.flow.first
Tff7b72import T7ee787kotlinx.coroutines.withTimeout
Tff7b72import T7ee787org.koin.core.annotation.Single
Tff7b72import T7ee787org.meshtastic.core.model.ConnectionState
Tff7b72import T7ee787org.meshtastic.core.model.NodeAddress
Tff7b72import T7ee787org.meshtastic.core.repository.NodeRepository
Tff7b72import T7ee787org.meshtastic.core.repository.PacketRepository
Tff7b72import T7ee787org.meshtastic.core.repository.RadioConfigRepository
Tff7b72import T7ee787org.meshtastic.core.repository.ServiceRepository
Tff7b72import T7ee787org.meshtastic.core.repository.usecase.SendMessageUseCase
Tff7b72import T7ee787kotlin.coroutines.cancellation.CancellationException
Tff7b72import T7ee787kotlin.time.Clock
Tff7b72import T7ee787kotlin.time.Duration.Companion.seconds
T8b949e/**
* Implementation of [AiFunctionProvider] that bridges AI function invocations to existing Meshtastic repositories and
* use cases.
*/
Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffTooManyFunctionsTa5d6ff"Tb4b4b4)
Tf0883e@SingleTb4b4b4(Te6edf3binds Tff7b72= Tff7b72[Te6edf3AiFunctionProviderTff7b72::Te6edf3classTff7b72]Tb4b4b4)
Tff7b72class T56d364AiFunctionProviderImplTb4b4b4(
Tff7b72private Tff7b72val Te6edf3serviceRepositoryTb4b4b4: Te6edf3ServiceRepositoryTb4b4b4,
Tff7b72private Tff7b72val Te6edf3nodeRepositoryTb4b4b4: Te6edf3NodeRepositoryTb4b4b4,
Tff7b72private Tff7b72val Te6edf3radioConfigRepositoryTb4b4b4: Te6edf3RadioConfigRepositoryTb4b4b4,
Tff7b72private Tff7b72val Te6edf3packetRepositoryTb4b4b4: Te6edf3PacketRepositoryTb4b4b4,
Tff7b72private Tff7b72val Te6edf3sendMessageUseCaseTb4b4b4: Te6edf3SendMessageUseCaseTb4b4b4,
Tff7b72private Tff7b72val Te6edf3fuzzyNameResolverTb4b4b4: Te6edf3FuzzyNameResolverTb4b4b4,
Tff7b72private Tff7b72val Te6edf3rateLimiterTb4b4b4: Te6edf3RateLimiterTb4b4b4,
Tff7b72private Tff7b72val Te6edf3clockTb4b4b4: Te6edf3ClockTb4b4b4,
Tb4b4b4) Tb4b4b4: Te6edf3AiFunctionProvider Tb4b4b4{
Tff7b72override Tff7b72suspend Tff7b72fun Td2a8ffsendMessageTb4b4b4(Te6edf3textTb4b4b4: Tffa657StringTb4b4b4, Te6edf3recipientNameTb4b4b4: Tffa657String?Tb4b4b4, Te6edf3channelNameTb4b4b4: Tffa657String?Tb4b4b4)Tb4b4b4: Te6edf3SendMessageResult Tff7b72=
Te6edf3withTimeoutTb4b4b4(Te6edf3OPERATION_TIMEOUTTb4b4b4) Tb4b4b4{
T8b949e// Check connection
Tff7b72if Tb4b4b4(Te6edf3serviceRepositoryTb4b4b4.Te6edf3connectionStateTb4b4b4.Te6edf3value Tff7b72!Tff7b72= Te6edf3ConnectionStateTb4b4b4.Te6edf3ConnectedTb4b4b4) Tb4b4b4{
Tff7b72returnTf0883e@withTimeout Te6edf3SendMessageResultTb4b4b4.Te6edf3NotConnectedTb4b4b4(
Ta5d6ff"Ta5d6ffNot connected to a Meshtastic radio. Please connect first.Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tb4b4b4}
T8b949e// Check rate limit
Tff7b72when Tb4b4b4(Tff7b72val Te6edf3rateResult Tff7b72= Te6edf3rateLimiterTb4b4b4.Te6edf3tryAcquireTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4{
Tff7b72is Te6edf3RateLimitResultTb4b4b4.Te6edf3Permitted Tff7b72-Tff7b72> Tb4b4b4{
T8b949e/* proceed */
Tb4b4b4}
Tff7b72is Te6edf3RateLimitResultTb4b4b4.Te6edf3Limited Tff7b72-Tff7b72> Tb4b4b4{
Tff7b72returnTf0883e@withTimeout Te6edf3SendMessageResultTb4b4b4.Te6edf3RateLimitedTb4b4b4(Te6edf3rateResultTb4b4b4.Te6edf3retryAfterSecondsTb4b4b4)
Tb4b4b4}
Tb4b4b4}
T8b949e// Validate message length
Tff7b72val Te6edf3messageBytes Tff7b72= Te6edf3textTb4b4b4.Te6edf3encodeToByteArrayTb4b4b4(Tb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3messageBytesTb4b4b4.Te6edf3size Tff7b72> Te6edf3MAX_MESSAGE_LENGTHTb4b4b4) Tb4b4b4{
Tff7b72returnTf0883e@withTimeout Te6edf3SendMessageResultTb4b4b4.Te6edf3InvalidArgumentTb4b4b4(
Ta5d6ff"Ta5d6ffMessage too long: Tffd700${Te6edf3messageBytesTb4b4b4.Te6edf3sizeTffd700}Ta5d6ff bytes exceeds maximum of Tffd700$Te6edf3MAX_MESSAGE_LENGTHTa5d6ff bytes.Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tb4b4b4}
T8b949e// Resolve destination
Tff7b72val Te6edf3contactKey Tff7b72=
Te6edf3resolveContactKeyTb4b4b4(Te6edf3recipientNameTb4b4b4, Te6edf3channelNameTb4b4b4)
Tff7b72?: Tff7b72returnTf0883e@withTimeout Te6edf3SendMessageResultTb4b4b4.Te6edf3InvalidArgumentTb4b4b4(Ta5d6ff"Ta5d6ffCould not resolve destination.Ta5d6ff"Tb4b4b4)
T8b949e// Handle ambiguous results from resolution
Tff7b72if Tb4b4b4(Te6edf3contactKey Tff7b72is Te6edf3ResolvedContactTb4b4b4.Te6edf3AmbiguousTb4b4b4) Tb4b4b4{
Tff7b72returnTf0883e@withTimeout Te6edf3SendMessageResultTb4b4b4.Te6edf3AmbiguousNameTb4b4b4(Te6edf3contactKeyTb4b4b4.Te6edf3candidatesTb4b4b4)
Tb4b4b4}
Tff7b72val Te6edf3key Tff7b72= Tb4b4b4(Te6edf3contactKey Tff7b72as Te6edf3ResolvedContactTb4b4b4.Te6edf3ResolvedTb4b4b4)Tb4b4b4.Te6edf3contactKey
T8b949e// Send via existing use case and capture the generated messageId
Tff7b72try Tb4b4b4{
Tff7b72val Te6edf3messageId Tff7b72= Te6edf3sendMessageUseCaseTb4b4b4.Te6edf3invokeTb4b4b4(Te6edf3textTb4b4b4, Te6edf3keyTb4b4b4)
Te6edf3SendMessageResultTb4b4b4.Te6edf3SuccessTb4b4b4(
Te6edf3messageId Tff7b72= Te6edf3messageIdTb4b4b4,
Te6edf3channel Tff7b72= Te6edf3contactKeyTb4b4b4.Te6edf3channelNameTb4b4b4,
Te6edf3timestamp Tff7b72= Te6edf3clockTb4b4b4.Te6edf3nowTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toEpochMillisecondsTb4b4b4(Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tb4b4b4} Tff7b72catch Tb4b4b4(Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffTooGenericExceptionCaughtTa5d6ff"Tb4b4b4) Te6edf3exTb4b4b4: Te6edf3ExceptionTb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3ex Tff7b72is Te6edf3CancellationExceptionTb4b4b4) Tff7b72throw Te6edf3ex
Te6edf3SendMessageResultTb4b4b4.Te6edf3InvalidArgumentTb4b4b4(Ta5d6ff"Ta5d6ffFailed to send message: Tffd700${Te6edf3exTb4b4b4.Te6edf3messageTffd700}Ta5d6ff"Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tff7b72override Tff7b72suspend Tff7b72fun Td2a8ffgetMeshStatusTb4b4b4(Tb4b4b4)Tb4b4b4: Te6edf3MeshStatusResult Tff7b72= Te6edf3withTimeoutTb4b4b4(Te6edf3OPERATION_TIMEOUTTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3connectionState Tff7b72= Te6edf3serviceRepositoryTb4b4b4.Te6edf3connectionStateTb4b4b4.Te6edf3value
Tff7b72val Te6edf3onlineCount Tff7b72= Te6edf3nodeRepositoryTb4b4b4.Te6edf3onlineNodeCountTb4b4b4.Te6edf3firstTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3totalCount Tff7b72= Te6edf3nodeRepositoryTb4b4b4.Te6edf3totalNodeCountTb4b4b4.Te6edf3firstTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3ourNode Tff7b72= Te6edf3nodeRepositoryTb4b4b4.Te6edf3ourNodeInfoTb4b4b4.Te6edf3value
Tff7b72val Te6edf3batteryLevel Tff7b72= Te6edf3ourNodeTff7b72?.Te6edf3batteryLevelTff7b72?.Te6edf3takeIf Tb4b4b4{ Tffa657it Tff7b72in T79c0ff1.Tb4b4b4.Te6edf3MAX_BATTERY_LEVEL Tb4b4b4}
Tff7b72val Te6edf3nodeName Tff7b72= Te6edf3ourNodeTff7b72?.Te6edf3userTff7b72?.Te6edf3long_nameTff7b72?.Te6edf3takeIf Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3isNotBlankTb4b4b4(Tb4b4b4) Tb4b4b4}
Te6edf3MeshStatusResultTb4b4b4(
Te6edf3connectionState Tff7b72= Te6edf3connectionStateTb4b4b4.Te6edf3nameTb4b4b4,
Te6edf3onlineNodeCount Tff7b72= Te6edf3onlineCountTb4b4b4,
Te6edf3totalNodeCount Tff7b72= Te6edf3totalCountTb4b4b4,
Te6edf3localBatteryLevel Tff7b72= Te6edf3batteryLevelTb4b4b4,
Te6edf3localNodeName Tff7b72= Te6edf3nodeNameTb4b4b4,
Tb4b4b4)
Tb4b4b4}
Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffReturnCountTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffTooGenericExceptionCaughtTa5d6ff"Tb4b4b4)
Tff7b72override Tff7b72suspend Tff7b72fun Td2a8ffgetNodeListTb4b4b4(Tb4b4b4)Tb4b4b4: Te6edf3GetNodeListResult Tff7b72= Te6edf3withTimeoutTb4b4b4(Te6edf3OPERATION_TIMEOUTTb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3serviceRepositoryTb4b4b4.Te6edf3connectionStateTb4b4b4.Te6edf3value Tff7b72!Tff7b72= Te6edf3ConnectionStateTb4b4b4.Te6edf3ConnectedTb4b4b4) Tb4b4b4{
Tff7b72returnTf0883e@withTimeout Te6edf3GetNodeListResultTb4b4b4.Te6edf3NotConnectedTb4b4b4(Ta5d6ff"Ta5d6ffNot connected to a Meshtastic radio.Ta5d6ff"Tb4b4b4)
Tb4b4b4}
Tff7b72try Tb4b4b4{
Tff7b72val Te6edf3nodeMap Tff7b72= Te6edf3nodeRepositoryTb4b4b4.Te6edf3nodeDBbyNumTb4b4b4.Te6edf3firstTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3nodes Tff7b72=
Te6edf3nodeMapTb4b4b4.Te6edf3valuesTb4b4b4.Te6edf3map Tb4b4b4{ Te6edf3node Tff7b72-Tff7b72>
Te6edf3NodeSummaryTb4b4b4(
Te6edf3id Tff7b72= Te6edf3NodeAddressTb4b4b4.Te6edf3numToDefaultIdTb4b4b4(Te6edf3nodeTb4b4b4.Te6edf3numTb4b4b4)Tb4b4b4,
Te6edf3name Tff7b72= Te6edf3nodeTb4b4b4.Te6edf3userTb4b4b4.Te6edf3long_nameTb4b4b4.Te6edf3takeIf Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3isNotBlankTb4b4b4(Tb4b4b4) Tb4b4b4} Tff7b72?: Ta5d6ff"Ta5d6ffNode Tffd700${Te6edf3nodeTb4b4b4.Te6edf3numTffd700}Ta5d6ff"Tb4b4b4,
Te6edf3batteryLevel Tff7b72= Te6edf3nodeTb4b4b4.Te6edf3deviceMetricsTb4b4b4.Te6edf3battery_levelTff7b72?.Te6edf3coerceInTb4b4b4(T79c0ff0Tb4b4b4, Te6edf3MAX_BATTERY_LEVELTb4b4b4)Tb4b4b4,
Te6edf3lastHeard Tff7b72= Te6edf3nodeTb4b4b4.Te6edf3lastHeardTb4b4b4.Te6edf3toLongTb4b4b4(Tb4b4b4) Tff7b72* Te6edf3MS_PER_SECTb4b4b4,
Te6edf3isOnline Tff7b72= Te6edf3nodeTb4b4b4.Te6edf3isOnlineTb4b4b4,
Tb4b4b4)
Tb4b4b4}
Te6edf3GetNodeListResultTb4b4b4.Te6edf3SuccessTb4b4b4(Te6edf3nodesTb4b4b4.Te6edf3sortedByDescending Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3lastHeard Tb4b4b4}Tb4b4b4)
Tb4b4b4} Tff7b72catch Tb4b4b4(Te6edf3exTb4b4b4: Te6edf3ExceptionTb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3ex Tff7b72is Te6edf3CancellationExceptionTb4b4b4) Tff7b72throw Te6edf3ex
Te6edf3GetNodeListResultTb4b4b4.Te6edf3ErrorTb4b4b4(Ta5d6ff"Ta5d6ffFailed to retrieve node list: Tffd700${Te6edf3exTb4b4b4.Te6edf3messageTffd700}Ta5d6ff"Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffReturnCountTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffTooGenericExceptionCaughtTa5d6ff"Tb4b4b4)
Tff7b72override Tff7b72suspend Tff7b72fun Td2a8ffgetChannelInfoTb4b4b4(Tb4b4b4)Tb4b4b4: Te6edf3GetChannelInfoResult Tff7b72= Te6edf3withTimeoutTb4b4b4(Te6edf3OPERATION_TIMEOUTTb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3serviceRepositoryTb4b4b4.Te6edf3connectionStateTb4b4b4.Te6edf3value Tff7b72!Tff7b72= Te6edf3ConnectionStateTb4b4b4.Te6edf3ConnectedTb4b4b4) Tb4b4b4{
Tff7b72returnTf0883e@withTimeout Te6edf3GetChannelInfoResultTb4b4b4.Te6edf3NotConnectedTb4b4b4(Ta5d6ff"Ta5d6ffNot connected to a Meshtastic radio.Ta5d6ff"Tb4b4b4)
Tb4b4b4}
Tff7b72try Tb4b4b4{
Tff7b72val Te6edf3channelSet Tff7b72= Te6edf3radioConfigRepositoryTb4b4b4.Te6edf3channelSetFlowTb4b4b4.Te6edf3firstTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3channels Tff7b72=
Te6edf3channelSetTb4b4b4.Te6edf3settingsTb4b4b4.Te6edf3mapIndexed Tb4b4b4{ Te6edf3indexTb4b4b4, Te6edf3channel Tff7b72-Tff7b72>
Te6edf3ChannelSummaryTb4b4b4(
Te6edf3index Tff7b72= Te6edf3indexTb4b4b4,
Te6edf3name Tff7b72= Te6edf3channelTb4b4b4.Te6edf3nameTb4b4b4.Te6edf3takeIf Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3isNotBlankTb4b4b4(Tb4b4b4) Tb4b4b4} Tff7b72?: Ta5d6ff"Ta5d6ffChannel Tffd700$Te6edf3indexTa5d6ff"Tb4b4b4,
Te6edf3isPrimary Tff7b72= Te6edf3index Tff7b72=Tff7b72= T79c0ff0Tb4b4b4,
Te6edf3uplinkEnabled Tff7b72= Te6edf3channelTb4b4b4.Te6edf3uplink_enabledTb4b4b4,
Te6edf3downlinkEnabled Tff7b72= Te6edf3channelTb4b4b4.Te6edf3downlink_enabledTb4b4b4,
Tb4b4b4)
Tb4b4b4}
Te6edf3GetChannelInfoResultTb4b4b4.Te6edf3SuccessTb4b4b4(Te6edf3channelsTb4b4b4)
Tb4b4b4} Tff7b72catch Tb4b4b4(Te6edf3exTb4b4b4: Te6edf3ExceptionTb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3ex Tff7b72is Te6edf3CancellationExceptionTb4b4b4) Tff7b72throw Te6edf3ex
Te6edf3GetChannelInfoResultTb4b4b4.Te6edf3ErrorTb4b4b4(Ta5d6ff"Ta5d6ffFailed to retrieve channel info: Tffd700${Te6edf3exTb4b4b4.Te6edf3messageTffd700}Ta5d6ff"Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffReturnCountTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffTooGenericExceptionCaughtTa5d6ff"Tb4b4b4)
Tff7b72override Tff7b72suspend Tff7b72fun Td2a8ffgetDeviceStatusTb4b4b4(Tb4b4b4)Tb4b4b4: Te6edf3GetDeviceStatusResult Tff7b72= Te6edf3withTimeoutTb4b4b4(Te6edf3OPERATION_TIMEOUTTb4b4b4) Tb4b4b4{
Tff7b72try Tb4b4b4{
Tff7b72val Te6edf3ourNode Tff7b72=
Te6edf3nodeRepositoryTb4b4b4.Te6edf3ourNodeInfoTb4b4b4.Te6edf3value
Tff7b72?: Tff7b72returnTf0883e@withTimeout Te6edf3GetDeviceStatusResultTb4b4b4.Te6edf3NotAvailableTb4b4b4(Ta5d6ff"Ta5d6ffDevice not yet initialized.Ta5d6ff"Tb4b4b4)
Tff7b72val Te6edf3deviceStatus Tff7b72=
Te6edf3DeviceStatusTb4b4b4(
Te6edf3model Tff7b72= Te6edf3ourNodeTb4b4b4.Te6edf3metadataTff7b72?.Te6edf3hw_modelTff7b72?.Te6edf3name Tff7b72?: Ta5d6ff"Ta5d6ffUnknownTa5d6ff"Tb4b4b4,
Te6edf3firmwareVersion Tff7b72= Te6edf3ourNodeTb4b4b4.Te6edf3metadataTff7b72?.Te6edf3firmware_version Tff7b72?: Ta5d6ff"Ta5d6ffUnknownTa5d6ff"Tb4b4b4,
Te6edf3batteryLevel Tff7b72= Te6edf3ourNodeTb4b4b4.Te6edf3deviceMetricsTb4b4b4.Te6edf3battery_levelTff7b72?.Te6edf3coerceInTb4b4b4(T79c0ff0Tb4b4b4, Te6edf3MAX_BATTERY_LEVELTb4b4b4)Tb4b4b4,
Te6edf3chargingStatus Tff7b72= Ta5d6ff"Ta5d6ffUNKNOWNTa5d6ff"Tb4b4b4,
Te6edf3deviceName Tff7b72= Te6edf3ourNodeTb4b4b4.Te6edf3userTb4b4b4.Te6edf3long_nameTb4b4b4.Te6edf3takeIf Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3isNotBlankTb4b4b4(Tb4b4b4) Tb4b4b4}Tb4b4b4,
Te6edf3isActive Tff7b72= Te6edf3serviceRepositoryTb4b4b4.Te6edf3connectionStateTb4b4b4.Te6edf3value Tff7b72=Tff7b72= Te6edf3ConnectionStateTb4b4b4.Te6edf3ConnectedTb4b4b4,
Tb4b4b4)
Te6edf3GetDeviceStatusResultTb4b4b4.Te6edf3SuccessTb4b4b4(Te6edf3deviceStatusTb4b4b4)
Tb4b4b4} Tff7b72catch Tb4b4b4(Te6edf3exTb4b4b4: Te6edf3ExceptionTb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3ex Tff7b72is Te6edf3CancellationExceptionTb4b4b4) Tff7b72throw Te6edf3ex
Te6edf3GetDeviceStatusResultTb4b4b4.Te6edf3ErrorTb4b4b4(Ta5d6ff"Ta5d6ffFailed to retrieve device status: Tffd700${Te6edf3exTb4b4b4.Te6edf3messageTffd700}Ta5d6ff"Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffReturnCountTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffTooGenericExceptionCaughtTa5d6ff"Tb4b4b4)
Tff7b72override Tff7b72suspend Tff7b72fun Td2a8ffgetNodeDetailsTb4b4b4(Te6edf3nodeIdTb4b4b4: Tffa657StringTb4b4b4)Tb4b4b4: Te6edf3GetNodeDetailsResult Tff7b72= Te6edf3withTimeoutTb4b4b4(Te6edf3OPERATION_TIMEOUTTb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3serviceRepositoryTb4b4b4.Te6edf3connectionStateTb4b4b4.Te6edf3value Tff7b72!Tff7b72= Te6edf3ConnectionStateTb4b4b4.Te6edf3ConnectedTb4b4b4) Tb4b4b4{
Tff7b72returnTf0883e@withTimeout Te6edf3GetNodeDetailsResultTb4b4b4.Te6edf3NotConnectedTb4b4b4(Ta5d6ff"Ta5d6ffNot connected to a Meshtastic radio.Ta5d6ff"Tb4b4b4)
Tb4b4b4}
Tff7b72try Tb4b4b4{
Tff7b72val Te6edf3node Tff7b72=
Tff7b72if Tb4b4b4(Te6edf3nodeIdTb4b4b4.Te6edf3startsWithTb4b4b4(Ta5d6ff"Ta5d6ff!Ta5d6ff"Tb4b4b4)Tb4b4b4) Tb4b4b4{
T8b949e// Canonical hex node ID (e.g. "!ffffffff"). idToNum parses the full unsigned 32-bit
T8b949e// range and returns null for malformed input, which we surface as NotFound.
Tff7b72val Te6edf3nodeNum Tff7b72=
Te6edf3NodeAddressTb4b4b4.Te6edf3idToNumTb4b4b4(Te6edf3nodeIdTb4b4b4)
Tff7b72?: Tff7b72returnTf0883e@withTimeout Te6edf3GetNodeDetailsResultTb4b4b4.Te6edf3NotFoundTb4b4b4(Ta5d6ff"Ta5d6ffNode not found: Tffd700$Te6edf3nodeIdTa5d6ff"Tb4b4b4)
Te6edf3nodeRepositoryTb4b4b4.Te6edf3nodeDBbyNumTb4b4b4.Te6edf3firstTb4b4b4(Tb4b4b4)Tff7b72[Te6edf3nodeNumTff7b72]
Tb4b4b4} Tff7b72else Tb4b4b4{
T8b949e// User ID format
Te6edf3nodeRepositoryTb4b4b4.Te6edf3getNodeTb4b4b4(Te6edf3nodeIdTb4b4b4)
Tb4b4b4}
Tff7b72if Tb4b4b4(Te6edf3node Tff7b72=Tff7b72= Tff7b72nullTb4b4b4) Tb4b4b4{
Tff7b72returnTf0883e@withTimeout Te6edf3GetNodeDetailsResultTb4b4b4.Te6edf3NotFoundTb4b4b4(Ta5d6ff"Ta5d6ffNode not found: Tffd700$Te6edf3nodeIdTa5d6ff"Tb4b4b4)
Tb4b4b4}
T8b949e// Check if position is valid (both coords zero AND time zero indicates no position fix)
Tff7b72val Te6edf3hasValidPosition Tff7b72= Te6edf3nodeTb4b4b4.Te6edf3latitude Tff7b72!Tff7b72= T79c0ff0.0 Tff7b72|Tff7b72| Te6edf3nodeTb4b4b4.Te6edf3longitude Tff7b72!Tff7b72= T79c0ff0.0 Tff7b72|Tff7b72| Te6edf3nodeTb4b4b4.Te6edf3positionTb4b4b4.Te6edf3time Tff7b72> T79c0ff0
Tff7b72val Te6edf3details Tff7b72=
Te6edf3NodeDetailsTb4b4b4(
Te6edf3id Tff7b72= Te6edf3NodeAddressTb4b4b4.Te6edf3numToDefaultIdTb4b4b4(Te6edf3nodeTb4b4b4.Te6edf3numTb4b4b4)Tb4b4b4,
Te6edf3userId Tff7b72= Te6edf3nodeTb4b4b4.Te6edf3userTb4b4b4.Te6edf3idTb4b4b4,
Te6edf3name Tff7b72= Te6edf3nodeTb4b4b4.Te6edf3userTb4b4b4.Te6edf3long_nameTb4b4b4.Te6edf3takeIf Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3isNotBlankTb4b4b4(Tb4b4b4) Tb4b4b4} Tff7b72?: Ta5d6ff"Ta5d6ffNode Tffd700${Te6edf3nodeTb4b4b4.Te6edf3numTffd700}Ta5d6ff"Tb4b4b4,
Te6edf3batteryLevel Tff7b72= Te6edf3nodeTb4b4b4.Te6edf3deviceMetricsTb4b4b4.Te6edf3battery_levelTff7b72?.Te6edf3coerceInTb4b4b4(T79c0ff0Tb4b4b4, Te6edf3MAX_BATTERY_LEVELTb4b4b4)Tb4b4b4,
Te6edf3voltage Tff7b72= Te6edf3nodeTb4b4b4.Te6edf3deviceMetricsTb4b4b4.Te6edf3voltageTb4b4b4,
Te6edf3hardwareModel Tff7b72= Te6edf3nodeTb4b4b4.Te6edf3metadataTff7b72?.Te6edf3hw_modelTff7b72?.Te6edf3name Tff7b72?: Ta5d6ff"Ta5d6ffUnknownTa5d6ff"Tb4b4b4,
Te6edf3firmwareVersion Tff7b72= Te6edf3nodeTb4b4b4.Te6edf3metadataTff7b72?.Te6edf3firmware_version Tff7b72?: Ta5d6ff"Ta5d6ffUnknownTa5d6ff"Tb4b4b4,
Te6edf3snr Tff7b72= Te6edf3nodeTb4b4b4.Te6edf3snrTb4b4b4,
Te6edf3rssi Tff7b72= Te6edf3nodeTb4b4b4.Te6edf3rssiTb4b4b4,
Te6edf3hopsAway Tff7b72= Te6edf3nodeTb4b4b4.Te6edf3hopsAwayTb4b4b4,
Te6edf3channel Tff7b72= Te6edf3nodeTb4b4b4.Te6edf3channelTb4b4b4,
Te6edf3lastHeard Tff7b72= Te6edf3nodeTb4b4b4.Te6edf3lastHeardTb4b4b4.Te6edf3toLongTb4b4b4(Tb4b4b4) Tff7b72* Te6edf3MS_PER_SECTb4b4b4,
Te6edf3userRole Tff7b72= Te6edf3nodeTb4b4b4.Te6edf3userTb4b4b4.Te6edf3roleTb4b4b4.Te6edf3nameTb4b4b4,
Te6edf3isLicensed Tff7b72= Te6edf3nodeTb4b4b4.Te6edf3userTb4b4b4.Te6edf3is_licensedTb4b4b4,
Te6edf3latitude Tff7b72= Te6edf3nodeTb4b4b4.Te6edf3latitudeTb4b4b4.Te6edf3takeIf Tb4b4b4{ Te6edf3hasValidPosition Tb4b4b4}Tb4b4b4,
Te6edf3longitude Tff7b72= Te6edf3nodeTb4b4b4.Te6edf3longitudeTb4b4b4.Te6edf3takeIf Tb4b4b4{ Te6edf3hasValidPosition Tb4b4b4}Tb4b4b4,
Tb4b4b4)
Te6edf3GetNodeDetailsResultTb4b4b4.Te6edf3SuccessTb4b4b4(Te6edf3detailsTb4b4b4)
Tb4b4b4} Tff7b72catch Tb4b4b4(Te6edf3exTb4b4b4: Te6edf3ExceptionTb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3ex Tff7b72is Te6edf3CancellationExceptionTb4b4b4) Tff7b72throw Te6edf3ex
Te6edf3GetNodeDetailsResultTb4b4b4.Te6edf3ErrorTb4b4b4(Ta5d6ff"Ta5d6ffFailed to retrieve node details: Tffd700${Te6edf3exTb4b4b4.Te6edf3messageTffd700}Ta5d6ff"Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffReturnCountTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffTooGenericExceptionCaughtTa5d6ff"Tb4b4b4)
Tff7b72override Tff7b72suspend Tff7b72fun Td2a8ffgetMeshMetricsTb4b4b4(Tb4b4b4)Tb4b4b4: Te6edf3GetMeshMetricsResult Tff7b72= Te6edf3withTimeoutTb4b4b4(Te6edf3OPERATION_TIMEOUTTb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3serviceRepositoryTb4b4b4.Te6edf3connectionStateTb4b4b4.Te6edf3value Tff7b72!Tff7b72= Te6edf3ConnectionStateTb4b4b4.Te6edf3ConnectedTb4b4b4) Tb4b4b4{
Tff7b72returnTf0883e@withTimeout Te6edf3GetMeshMetricsResultTb4b4b4.Te6edf3NotConnectedTb4b4b4(Ta5d6ff"Ta5d6ffNot connected to a Meshtastic radio.Ta5d6ff"Tb4b4b4)
Tb4b4b4}
Tff7b72try Tb4b4b4{
Tff7b72val Te6edf3totalCount Tff7b72= Te6edf3nodeRepositoryTb4b4b4.Te6edf3totalNodeCountTb4b4b4.Te6edf3firstTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3onlineCount Tff7b72= Te6edf3nodeRepositoryTb4b4b4.Te6edf3onlineNodeCountTb4b4b4.Te6edf3firstTb4b4b4(Tb4b4b4)
T8b949e// Calculate average battery level
Tff7b72val Te6edf3nodeMap Tff7b72= Te6edf3nodeRepositoryTb4b4b4.Te6edf3nodeDBbyNumTb4b4b4.Te6edf3firstTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3batteryLevels Tff7b72= Te6edf3nodeMapTb4b4b4.Te6edf3valuesTb4b4b4.Te6edf3mapNotNull Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3deviceMetricsTb4b4b4.Te6edf3battery_level Tb4b4b4}
Tff7b72val Te6edf3avgBattery Tff7b72=
Tff7b72if Tb4b4b4(Te6edf3batteryLevelsTb4b4b4.Te6edf3isNotEmptyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4{
Tb4b4b4(Te6edf3batteryLevelsTb4b4b4.Te6edf3sumTb4b4b4(Tb4b4b4) Tff7b72/ Te6edf3batteryLevelsTb4b4b4.Te6edf3sizeTb4b4b4)Tb4b4b4.Te6edf3coerceInTb4b4b4(T79c0ff0Tb4b4b4, Te6edf3MAX_BATTERY_LEVELTb4b4b4)
Tb4b4b4} Tff7b72else Tb4b4b4{
Tff7b72null
Tb4b4b4}
T8b949e// Mesh health score: 0-100 based on online ratio and recent activity
Tff7b72val Te6edf3healthScore Tff7b72=
Tff7b72when Tb4b4b4{
Te6edf3totalCount Tff7b72=Tff7b72= T79c0ff0 Tff7b72-Tff7b72> T79c0ff0
Te6edf3onlineCount Tff7b72=Tff7b72= T79c0ff0 Tff7b72-Tff7b72> Te6edf3HEALTH_SCORE_DEGRADED
Tff7b72else Tff7b72-Tff7b72> Tb4b4b4(Te6edf3HEALTH_SCORE_BASE Tff7b72+ Tb4b4b4(Te6edf3HEALTH_SCORE_ONLINE_RATIO Tff7b72* Te6edf3onlineCountTb4b4b4) Tff7b72/ Te6edf3totalCountTb4b4b4)Tb4b4b4.Te6edf3toIntTb4b4b4(Tb4b4b4)
Tb4b4b4}
T8b949e// Find most recent packet: max lastHeard across all nodes (convert seconds to ms)
Tff7b72val Te6edf3mostRecentPacketTimeMs Tff7b72=
Te6edf3nodeMapTb4b4b4.Te6edf3valuesTb4b4b4.Te6edf3maxOfOrNull Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3lastHeard Tb4b4b4}Tff7b72?.Te6edf3takeIf Tb4b4b4{ Tffa657it Tff7b72> T79c0ff0 Tb4b4b4}Tff7b72?.Te6edf3toLongTb4b4b4(Tb4b4b4)Tff7b72?.Te6edf3timesTb4b4b4(Te6edf3MS_PER_SECTb4b4b4)
Tff7b72?: Te6edf3clockTb4b4b4.Te6edf3nowTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toEpochMillisecondsTb4b4b4(Tb4b4b4)
T8b949e// Get local device uptime from its DeviceMetrics (node #0 is typically the local device)
Tff7b72val Te6edf3localNode Tff7b72= Te6edf3nodeMapTb4b4b4.Te6edf3valuesTb4b4b4.Te6edf3find Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3num Tff7b72=Tff7b72= T79c0ff0 Tb4b4b4} Tff7b72?: Te6edf3nodeMapTb4b4b4.Te6edf3valuesTb4b4b4.Te6edf3firstOrNullTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3meshUptimeSeconds Tff7b72= Te6edf3localNodeTff7b72?.Te6edf3deviceMetricsTff7b72?.Te6edf3uptime_secondsTff7b72?.Te6edf3toLongTb4b4b4(Tb4b4b4) Tff7b72?: T79c0ff0L
Tff7b72val Te6edf3metrics Tff7b72=
Te6edf3MeshMetricsTb4b4b4(
Te6edf3totalNodeCount Tff7b72= Te6edf3totalCountTb4b4b4,
Te6edf3onlineNodeCount Tff7b72= Te6edf3onlineCountTb4b4b4,
Te6edf3averageBatteryLevel Tff7b72= Te6edf3avgBatteryTb4b4b4,
Te6edf3meshHealthScore Tff7b72= Te6edf3healthScoreTb4b4b4.Te6edf3coerceInTb4b4b4(T79c0ff0Tb4b4b4, Te6edf3HEALTH_SCORE_MAXTb4b4b4)Tb4b4b4,
Te6edf3mostRecentPacketTime Tff7b72= Te6edf3mostRecentPacketTimeMsTb4b4b4,
Te6edf3meshUptimeSeconds Tff7b72= Te6edf3meshUptimeSecondsTb4b4b4,
Te6edf3channelUtilizationPercent Tff7b72= Tff7b72nullTb4b4b4, T8b949e// Could compute from radioConfigRepository if needed
Tb4b4b4)
Te6edf3GetMeshMetricsResultTb4b4b4.Te6edf3SuccessTb4b4b4(Te6edf3metricsTb4b4b4)
Tb4b4b4} Tff7b72catch Tb4b4b4(Te6edf3exTb4b4b4: Te6edf3ExceptionTb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3ex Tff7b72is Te6edf3CancellationExceptionTb4b4b4) Tff7b72throw Te6edf3ex
Te6edf3GetMeshMetricsResultTb4b4b4.Te6edf3ErrorTb4b4b4(Ta5d6ff"Ta5d6ffFailed to retrieve mesh metrics: Tffd700${Te6edf3exTb4b4b4.Te6edf3messageTffd700}Ta5d6ff"Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffReturnCountTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffTooGenericExceptionCaughtTa5d6ff"Tb4b4b4)
Tff7b72override Tff7b72suspend Tff7b72fun Td2a8ffgetRecentMessagesTb4b4b4(Te6edf3contactNameTb4b4b4: Tffa657String?Tb4b4b4, Te6edf3limitTb4b4b4: Tffa657IntTb4b4b4)Tb4b4b4: Te6edf3GetRecentMessagesResult Tff7b72=
Te6edf3withTimeoutTb4b4b4(Te6edf3OPERATION_TIMEOUTTb4b4b4) Tb4b4b4{
Tff7b72try Tb4b4b4{
Tff7b72val Te6edf3effectiveLimit Tff7b72= Te6edf3limitTb4b4b4.Te6edf3coerceInTb4b4b4(T79c0ff1Tb4b4b4, Te6edf3AiFunctionProviderTb4b4b4.Te6edf3MAX_MESSAGE_LIMITTb4b4b4)
T8b949e// Resolve contact key if a name filter is provided
Tff7b72val Te6edf3contactKey Tff7b72=
Tff7b72if Tb4b4b4(Te6edf3contactName Tff7b72!Tff7b72= Tff7b72nullTb4b4b4) Tb4b4b4{
Te6edf3resolveContactKeyForReadTb4b4b4(Te6edf3contactNameTb4b4b4)
Tff7b72?: Tff7b72returnTf0883e@withTimeout Te6edf3GetRecentMessagesResultTb4b4b4.Te6edf3ContactNotFoundTb4b4b4(
Ta5d6ff"Ta5d6ffContact not found: Tffd700$Te6edf3contactNameTa5d6ff"Tb4b4b4,
Tb4b4b4)
Tb4b4b4} Tff7b72else Tb4b4b4{
Tff7b72null
Tb4b4b4}
Tff7b72val Te6edf3messages Tff7b72=
Tff7b72if Tb4b4b4(Te6edf3contactKey Tff7b72!Tff7b72= Tff7b72nullTb4b4b4) Tb4b4b4{
T8b949e// Fetch messages from a specific contact
Te6edf3packetRepository
Tb4b4b4.Te6edf3getMessagesFromTb4b4b4(
Te6edf3contact Tff7b72= Te6edf3contactKeyTb4b4b4,
Te6edf3limit Tff7b72= Te6edf3effectiveLimitTb4b4b4,
Te6edf3includeFiltered Tff7b72= Tff7b72falseTb4b4b4,
Te6edf3getNode Tff7b72= Tb4b4b4{ Te6edf3userId Tff7b72-Tff7b72> Te6edf3nodeRepositoryTb4b4b4.Te6edf3getNodeTb4b4b4(Te6edf3userId Tff7b72?: Ta5d6ff"Ta5d6ff"Tb4b4b4) Tb4b4b4}Tb4b4b4,
Tb4b4b4)
Tb4b4b4.Te6edf3firstTb4b4b4(Tb4b4b4)
Tb4b4b4} Tff7b72else Tb4b4b4{
T8b949e// Fetch recent messages across all contacts
Tff7b72val Te6edf3contacts Tff7b72= Te6edf3packetRepositoryTb4b4b4.Te6edf3getContactsTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3firstTb4b4b4(Tb4b4b4)
Te6edf3contactsTb4b4b4.Te6edf3keys
Tb4b4b4.Te6edf3flatMap Tb4b4b4{ Te6edf3key Tff7b72-Tff7b72>
Te6edf3packetRepository
Tb4b4b4.Te6edf3getMessagesFromTb4b4b4(
Te6edf3contact Tff7b72= Te6edf3keyTb4b4b4,
Te6edf3limit Tff7b72= Te6edf3MESSAGES_PER_CONTACTTb4b4b4,
Te6edf3includeFiltered Tff7b72= Tff7b72falseTb4b4b4,
Te6edf3getNode Tff7b72= Tb4b4b4{ Te6edf3userId Tff7b72-Tff7b72> Te6edf3nodeRepositoryTb4b4b4.Te6edf3getNodeTb4b4b4(Te6edf3userId Tff7b72?: Ta5d6ff"Ta5d6ff"Tb4b4b4) Tb4b4b4}Tb4b4b4,
Tb4b4b4)
Tb4b4b4.Te6edf3firstTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tb4b4b4.Te6edf3sortedByDescending Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3receivedTime Tb4b4b4}
Tb4b4b4.Te6edf3takeTb4b4b4(Te6edf3effectiveLimitTb4b4b4)
Tb4b4b4}
Tff7b72val Te6edf3channelSet Tff7b72= Te6edf3radioConfigRepositoryTb4b4b4.Te6edf3channelSetFlowTb4b4b4.Te6edf3firstTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3summaries Tff7b72=
Te6edf3messagesTb4b4b4.Te6edf3map Tb4b4b4{ Te6edf3msg Tff7b72-Tff7b72>
Te6edf3MessageSummaryTb4b4b4(
Te6edf3senderName Tff7b72= Te6edf3msgTb4b4b4.Te6edf3nodeTb4b4b4.Te6edf3userTb4b4b4.Te6edf3long_nameTb4b4b4.Te6edf3takeIf Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3isNotBlankTb4b4b4(Tb4b4b4) Tb4b4b4} Tff7b72?: Ta5d6ff"Ta5d6ffNode Tffd700${Te6edf3msgTb4b4b4.Te6edf3nodeTb4b4b4.Te6edf3numTffd700}Ta5d6ff"Tb4b4b4,
Te6edf3text Tff7b72= Te6edf3msgTb4b4b4.Te6edf3textTb4b4b4,
Te6edf3contactName Tff7b72= Te6edf3resolveContactDisplayNameTb4b4b4(Te6edf3msgTb4b4b4, Te6edf3channelSetTb4b4b4)Tb4b4b4,
Te6edf3receivedTime Tff7b72= Te6edf3msgTb4b4b4.Te6edf3receivedTimeTb4b4b4,
Te6edf3fromLocal Tff7b72= Te6edf3msgTb4b4b4.Te6edf3fromLocalTb4b4b4,
Te6edf3read Tff7b72= Te6edf3msgTb4b4b4.Te6edf3readTb4b4b4,
Tb4b4b4)
Tb4b4b4}
Te6edf3GetRecentMessagesResultTb4b4b4.Te6edf3SuccessTb4b4b4(Te6edf3summariesTb4b4b4)
Tb4b4b4} Tff7b72catch Tb4b4b4(Te6edf3exTb4b4b4: Te6edf3ExceptionTb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3ex Tff7b72is Te6edf3CancellationExceptionTb4b4b4) Tff7b72throw Te6edf3ex
Te6edf3GetRecentMessagesResultTb4b4b4.Te6edf3ErrorTb4b4b4(Ta5d6ff"Ta5d6ffFailed to retrieve messages: Tffd700${Te6edf3exTb4b4b4.Te6edf3messageTffd700}Ta5d6ff"Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffTooGenericExceptionCaughtTa5d6ff"Tb4b4b4)
Tff7b72override Tff7b72suspend Tff7b72fun Td2a8ffgetUnreadSummaryTb4b4b4(Tb4b4b4)Tb4b4b4: Te6edf3GetUnreadSummaryResult Tff7b72= Te6edf3withTimeoutTb4b4b4(Te6edf3OPERATION_TIMEOUTTb4b4b4) Tb4b4b4{
Tff7b72try Tb4b4b4{
Tff7b72val Te6edf3contacts Tff7b72= Te6edf3packetRepositoryTb4b4b4.Te6edf3getContactsTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3firstTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3settings Tff7b72= Te6edf3packetRepositoryTb4b4b4.Te6edf3getContactSettingsTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3firstTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3channelSet Tff7b72= Te6edf3radioConfigRepositoryTb4b4b4.Te6edf3channelSetFlowTb4b4b4.Te6edf3firstTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3nodeMap Tff7b72= Te6edf3nodeRepositoryTb4b4b4.Te6edf3nodeDBbyNumTb4b4b4.Te6edf3firstTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3nonMutedContacts Tff7b72= Te6edf3contactsTb4b4b4.Te6edf3filter Tb4b4b4{ Tb4b4b4(Te6edf3keyTb4b4b4, Te6edf3_Tb4b4b4) Tff7b72-Tff7b72> Te6edf3settingsTff7b72[Te6edf3keyTff7b72]Tff7b72?.Te6edf3isMuted Tff7b72!Tff7b72= Tff7b72true Tb4b4b4}
Tff7b72val Te6edf3contactUnreads Tff7b72=
Te6edf3nonMutedContactsTb4b4b4.Te6edf3mapNotNull Tb4b4b4{ Tb4b4b4(Te6edf3contactKeyTb4b4b4, Te6edf3lastPacketTb4b4b4) Tff7b72-Tff7b72>
Tff7b72val Te6edf3unreadCount Tff7b72= Te6edf3packetRepositoryTb4b4b4.Te6edf3getUnreadCountTb4b4b4(Te6edf3contactKeyTb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3unreadCount Tff7b72<Tff7b72= T79c0ff0Tb4b4b4) Tff7b72returnTf0883e@mapNotNull Tff7b72null
Tff7b72val Te6edf3isBroadcast Tff7b72= Te6edf3lastPacketTb4b4b4.Te6edf3to Tff7b72=Tff7b72= Te6edf3NodeAddressTb4b4b4.Te6edf3ID_BROADCAST
Tff7b72val Te6edf3displayName Tff7b72=
Tff7b72if Tb4b4b4(Te6edf3isBroadcastTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3channelIndex Tff7b72= Te6edf3contactKeyTb4b4b4.Te6edf3firstOrNullTb4b4b4(Tb4b4b4)Tff7b72?.Te6edf3digitToIntOrNullTb4b4b4(Tb4b4b4) Tff7b72?: T79c0ff0
Te6edf3channelSetTb4b4b4.Te6edf3settingsTb4b4b4.Te6edf3getOrNullTb4b4b4(Te6edf3channelIndexTb4b4b4)Tff7b72?.Te6edf3nameTff7b72?.Te6edf3ifBlank Tb4b4b4{ Ta5d6ff"Ta5d6ffChannel Tffd700$Te6edf3channelIndexTa5d6ff" Tb4b4b4}
Tff7b72?: Ta5d6ff"Ta5d6ffChannel Tffd700$Te6edf3channelIndexTa5d6ff"
Tb4b4b4} Tff7b72else Tb4b4b4{
Tff7b72val Te6edf3userId Tff7b72= Te6edf3lastPacketTb4b4b4.Te6edf3from Tff7b72?: Ta5d6ff"Ta5d6ff"
Tff7b72val Te6edf3node Tff7b72= Te6edf3nodeMapTb4b4b4.Te6edf3valuesTb4b4b4.Te6edf3find Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3userTb4b4b4.Te6edf3id Tff7b72=Tff7b72= Te6edf3userId Tb4b4b4}
Te6edf3nodeTff7b72?.Te6edf3userTff7b72?.Te6edf3long_nameTff7b72?.Te6edf3takeIf Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3isNotBlankTb4b4b4(Tb4b4b4) Tb4b4b4} Tff7b72?: Ta5d6ff"Ta5d6ffUnknownTa5d6ff"
Tb4b4b4}
Te6edf3ContactUnreadTb4b4b4(
Te6edf3name Tff7b72= Te6edf3displayNameTb4b4b4,
Te6edf3unreadCount Tff7b72= Te6edf3unreadCountTb4b4b4,
Te6edf3lastMessagePreview Tff7b72= Te6edf3lastPacketTb4b4b4.Te6edf3textTff7b72?.Te6edf3takeTb4b4b4(Te6edf3MESSAGE_PREVIEW_MAX_LENGTHTb4b4b4)Tb4b4b4,
Te6edf3lastMessageTime Tff7b72= Te6edf3lastPacketTb4b4b4.Te6edf3timeTb4b4b4.Te6edf3takeIf Tb4b4b4{ Tffa657it Tff7b72> T79c0ff0 Tb4b4b4}Tb4b4b4,
Tb4b4b4)
Tb4b4b4}
Tff7b72val Te6edf3totalUnread Tff7b72= Te6edf3contactUnreadsTb4b4b4.Te6edf3sumOf Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3unreadCount Tb4b4b4}
Te6edf3GetUnreadSummaryResultTb4b4b4.Te6edf3SuccessTb4b4b4(
Te6edf3UnreadSummaryTb4b4b4(
Te6edf3totalUnreadCount Tff7b72= Te6edf3totalUnreadTb4b4b4,
Te6edf3contacts Tff7b72= Te6edf3contactUnreadsTb4b4b4.Te6edf3sortedByDescending Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3lastMessageTime Tb4b4b4}Tb4b4b4,
Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tb4b4b4} Tff7b72catch Tb4b4b4(Te6edf3exTb4b4b4: Te6edf3ExceptionTb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3ex Tff7b72is Te6edf3CancellationExceptionTb4b4b4) Tff7b72throw Te6edf3ex
Te6edf3GetUnreadSummaryResultTb4b4b4.Te6edf3ErrorTb4b4b4(Ta5d6ff"Ta5d6ffFailed to retrieve unread summary: Tffd700${Te6edf3exTb4b4b4.Te6edf3messageTffd700}Ta5d6ff"Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
T8b949e/**
* Resolve a contact name (node or channel) to a contact key for reading messages. Returns null if the name cannot
* be resolved.
*/
Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffReturnCountTa5d6ff"Tb4b4b4)
Tff7b72private Tff7b72suspend Tff7b72fun Td2a8ffresolveContactKeyForReadTb4b4b4(Te6edf3nameTb4b4b4: Tffa657StringTb4b4b4)Tb4b4b4: Tffa657String? Tb4b4b4{
T8b949e// Try node name first
Tff7b72when Tb4b4b4(Tff7b72val Te6edf3nodeResult Tff7b72= Te6edf3fuzzyNameResolverTb4b4b4.Te6edf3resolveNodeNameTb4b4b4(Te6edf3nameTb4b4b4)Tb4b4b4) Tb4b4b4{
Tff7b72is Te6edf3NodeNameResultTb4b4b4.Te6edf3Found Tff7b72-Tff7b72> Tb4b4b4{
Tff7b72val Te6edf3channelIndex Tff7b72= Te6edf3NodeAddressTb4b4b4.Te6edf3PKC_CHANNEL_INDEX
Tff7b72return Ta5d6ff"Tffd700${Te6edf3channelIndexTffd700}Tffd700${Te6edf3nodeResultTb4b4b4.Te6edf3userIdTffd700}Ta5d6ff"
Tb4b4b4}
Tff7b72is Te6edf3NodeNameResultTb4b4b4.Te6edf3Ambiguous Tff7b72-Tff7b72> Tff7b72return Tff7b72null
Tff7b72is Te6edf3NodeNameResultTb4b4b4.Te6edf3NotFound Tff7b72-Tff7b72> Tb4b4b4{
T8b949e/* fall through to channel */
Tb4b4b4}
Tb4b4b4}
T8b949e// Try channel name
Tff7b72return Tff7b72when Tb4b4b4(Tff7b72val Te6edf3channelResult Tff7b72= Te6edf3fuzzyNameResolverTb4b4b4.Te6edf3resolveChannelNameTb4b4b4(Te6edf3nameTb4b4b4)Tb4b4b4) Tb4b4b4{
Tff7b72is Te6edf3ChannelNameResultTb4b4b4.Te6edf3Found Tff7b72-Tff7b72> Ta5d6ff"Tffd700${Te6edf3channelResultTb4b4b4.Te6edf3channelIndexTffd700}Tffd700${Te6edf3NodeAddressTb4b4b4.Te6edf3ID_BROADCASTTffd700}Ta5d6ff"
Tff7b72is Te6edf3ChannelNameResultTb4b4b4.Te6edf3Ambiguous Tff7b72-Tff7b72> Tff7b72null
Tff7b72is Te6edf3ChannelNameResultTb4b4b4.Te6edf3NotFound Tff7b72-Tff7b72> Tff7b72null
Tb4b4b4}
Tb4b4b4}
Tff7b72private Tff7b72fun Td2a8ffresolveContactDisplayNameTb4b4b4(
Te6edf3msgTb4b4b4: Te6edf3orgTb4b4b4.Te6edf3meshtasticTb4b4b4.Te6edf3coreTb4b4b4.Te6edf3modelTb4b4b4.Te6edf3MessageTb4b4b4,
Te6edf3channelSetTb4b4b4: Te6edf3orgTb4b4b4.Te6edf3meshtasticTb4b4b4.Te6edf3protoTb4b4b4.Te6edf3ChannelSetTb4b4b4,
Tb4b4b4)Tb4b4b4: Tffa657String Tb4b4b4{
T8b949e// For broadcast messages, use channel name
Tff7b72val Te6edf3channelIndex Tff7b72= Te6edf3msgTb4b4b4.Te6edf3nodeTb4b4b4.Te6edf3channel
Tff7b72return Te6edf3channelSetTb4b4b4.Te6edf3settingsTb4b4b4.Te6edf3getOrNullTb4b4b4(Te6edf3channelIndexTb4b4b4)Tff7b72?.Te6edf3nameTff7b72?.Te6edf3ifBlank Tb4b4b4{ Ta5d6ff"Ta5d6ffChannel Tffd700$Te6edf3channelIndexTa5d6ff" Tb4b4b4}
Tff7b72?: Ta5d6ff"Ta5d6ffChannel Tffd700$Te6edf3channelIndexTa5d6ff"
Tb4b4b4}
Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffReturnCountTa5d6ff"Tb4b4b4)
Tff7b72private Tff7b72suspend Tff7b72fun Td2a8ffresolveContactKeyTb4b4b4(Te6edf3recipientNameTb4b4b4: Tffa657String?Tb4b4b4, Te6edf3channelNameTb4b4b4: Tffa657String?Tb4b4b4)Tb4b4b4: Te6edf3ResolvedContact? Tb4b4b4{
T8b949e// Direct message to a specific node
Tff7b72if Tb4b4b4(Te6edf3recipientName Tff7b72!Tff7b72= Tff7b72nullTb4b4b4) Tb4b4b4{
Tff7b72return Tff7b72when Tb4b4b4(Tff7b72val Te6edf3result Tff7b72= Te6edf3fuzzyNameResolverTb4b4b4.Te6edf3resolveNodeNameTb4b4b4(Te6edf3recipientNameTb4b4b4)Tb4b4b4) Tb4b4b4{
Tff7b72is Te6edf3NodeNameResultTb4b4b4.Te6edf3Found Tff7b72-Tff7b72> Tb4b4b4{
T8b949e// DM contact key format: channel_index + nodeId
T8b949e// For PKC DMs, use channel index 8; for legacy use no channel prefix
Tff7b72val Te6edf3channelIndex Tff7b72= Te6edf3NodeAddressTb4b4b4.Te6edf3PKC_CHANNEL_INDEX
Te6edf3ResolvedContactTb4b4b4.Te6edf3ResolvedTb4b4b4(
Te6edf3contactKey Tff7b72= Ta5d6ff"Tffd700${Te6edf3channelIndexTffd700}Tffd700${Te6edf3resultTb4b4b4.Te6edf3userIdTffd700}Ta5d6ff"Tb4b4b4,
Te6edf3channelName Tff7b72= Ta5d6ff"Ta5d6ffDM to Tffd700$Te6edf3recipientNameTa5d6ff"Tb4b4b4,
Tb4b4b4)
Tb4b4b4}
Tff7b72is Te6edf3NodeNameResultTb4b4b4.Te6edf3Ambiguous Tff7b72-Tff7b72> Te6edf3ResolvedContactTb4b4b4.Te6edf3AmbiguousTb4b4b4(Te6edf3resultTb4b4b4.Te6edf3candidatesTb4b4b4)
Tff7b72is Te6edf3NodeNameResultTb4b4b4.Te6edf3NotFound Tff7b72-Tff7b72> Tb4b4b4{
Tff7b72return Tff7b72null
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
T8b949e// Broadcast to a specific channel
Tff7b72if Tb4b4b4(Te6edf3channelName Tff7b72!Tff7b72= Tff7b72nullTb4b4b4) Tb4b4b4{
Tff7b72return Tff7b72when Tb4b4b4(Tff7b72val Te6edf3result Tff7b72= Te6edf3fuzzyNameResolverTb4b4b4.Te6edf3resolveChannelNameTb4b4b4(Te6edf3channelNameTb4b4b4)Tb4b4b4) Tb4b4b4{
Tff7b72is Te6edf3ChannelNameResultTb4b4b4.Te6edf3Found Tff7b72-Tff7b72>
Te6edf3ResolvedContactTb4b4b4.Te6edf3ResolvedTb4b4b4(
Te6edf3contactKey Tff7b72= Ta5d6ff"Tffd700${Te6edf3resultTb4b4b4.Te6edf3channelIndexTffd700}Tffd700${Te6edf3NodeAddressTb4b4b4.Te6edf3ID_BROADCASTTffd700}Ta5d6ff"Tb4b4b4,
Te6edf3channelName Tff7b72= Te6edf3resultTb4b4b4.Te6edf3nameTb4b4b4,
Tb4b4b4)
Tff7b72is Te6edf3ChannelNameResultTb4b4b4.Te6edf3Ambiguous Tff7b72-Tff7b72> Te6edf3ResolvedContactTb4b4b4.Te6edf3AmbiguousTb4b4b4(Te6edf3resultTb4b4b4.Te6edf3candidatesTb4b4b4)
Tff7b72is Te6edf3ChannelNameResultTb4b4b4.Te6edf3NotFound Tff7b72-Tff7b72> Tff7b72null
Tb4b4b4}
Tb4b4b4}
T8b949e// Default: broadcast on primary channel (index 0)
Tff7b72val Te6edf3channelSet Tff7b72= Te6edf3radioConfigRepositoryTb4b4b4.Te6edf3channelSetFlowTb4b4b4.Te6edf3firstTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3primaryName Tff7b72= Te6edf3channelSetTb4b4b4.Te6edf3settingsTb4b4b4.Te6edf3firstOrNullTb4b4b4(Tb4b4b4)Tff7b72?.Te6edf3nameTff7b72?.Te6edf3ifBlank Tb4b4b4{ Ta5d6ff"Ta5d6ffPrimaryTa5d6ff" Tb4b4b4} Tff7b72?: Ta5d6ff"Ta5d6ffPrimaryTa5d6ff"
Tff7b72return Te6edf3ResolvedContactTb4b4b4.Te6edf3ResolvedTb4b4b4(Te6edf3contactKey Tff7b72= Ta5d6ff"Ta5d6ff0Tffd700${Te6edf3NodeAddressTb4b4b4.Te6edf3ID_BROADCASTTffd700}Ta5d6ff"Tb4b4b4, Te6edf3channelName Tff7b72= Te6edf3primaryNameTb4b4b4)
Tb4b4b4}
Tff7b72private Tff7b72sealed Tff7b72class T56d364ResolvedContact Tb4b4b4{
Tff7b72data Tff7b72class T56d364ResolvedTb4b4b4(Tff7b72val Te6edf3contactKeyTb4b4b4: Tffa657StringTb4b4b4, Tff7b72val Te6edf3channelNameTb4b4b4: Tffa657StringTb4b4b4) Tb4b4b4: Te6edf3ResolvedContactTb4b4b4(Tb4b4b4)
Tff7b72data Tff7b72class T56d364AmbiguousTb4b4b4(Tff7b72val Te6edf3candidatesTb4b4b4: Te6edf3ListTff7b72<Tffa657StringTff7b72>Tb4b4b4) Tb4b4b4: Te6edf3ResolvedContactTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tff7b72companion Tff7b72object Tb4b4b4{
Tff7b72private Tff7b72val Te6edf3OPERATION_TIMEOUT Tff7b72= T79c0ff5.Te6edf3seconds
Tff7b72private Tff7b72const Tff7b72val Te6edf3MAX_BATTERY_LEVEL Tff7b72= T79c0ff1T79c0ff0T79c0ff0
Tff7b72private Tff7b72const Tff7b72val Te6edf3MS_PER_SEC Tff7b72= T79c0ff1T79c0ff0T79c0ff0T79c0ff0L
Tff7b72private Tff7b72const Tff7b72val Te6edf3HEALTH_SCORE_BASE Tff7b72= T79c0ff5T79c0ff0
Tff7b72private Tff7b72const Tff7b72val Te6edf3HEALTH_SCORE_ONLINE_RATIO Tff7b72= T79c0ff5T79c0ff0
Tff7b72private Tff7b72const Tff7b72val Te6edf3HEALTH_SCORE_DEGRADED Tff7b72= T79c0ff1T79c0ff0
Tff7b72private Tff7b72const Tff7b72val Te6edf3HEALTH_SCORE_MAX Tff7b72= T79c0ff1T79c0ff0T79c0ff0
Tff7b72private Tff7b72const Tff7b72val Te6edf3MESSAGES_PER_CONTACT Tff7b72= T79c0ff5
Tff7b72private Tff7b72const Tff7b72val Te6edf3MESSAGE_PREVIEW_MAX_LENGTH Tff7b72= T79c0ff1T79c0ff0T79c0ff0
T8b949e/** Standard Meshtastic message payload limit (bytes). */
Tff7b72const Tff7b72val Te6edf3MAX_MESSAGE_LENGTH Tff7b72= T79c0ff2T79c0ff3T79c0ff7
Tb4b4b4}
Tb4b4b4}
T8b949e/** Extension to get a display name for ConnectionState. */
Tff7b72private Tff7b72val Te6edf3ConnectionStateTb4b4b4.Te6edf3nameTb4b4b4: Tffa657String
Tff7b72getTb4b4b4(Tb4b4b4) Tff7b72=
Tff7b72when Tb4b4b4(Tff7b72thisTb4b4b4) Tb4b4b4{
Te6edf3ConnectionStateTb4b4b4.Te6edf3Connected Tff7b72-Tff7b72> Ta5d6ff"Ta5d6ffCONNECTEDTa5d6ff"
Te6edf3ConnectionStateTb4b4b4.Te6edf3Connecting Tff7b72-Tff7b72> Ta5d6ff"Ta5d6ffCONNECTINGTa5d6ff"
Te6edf3ConnectionStateTb4b4b4.Te6edf3Disconnected Tff7b72-Tff7b72> Ta5d6ff"Ta5d6ffDISCONNECTEDTa5d6ff"
Te6edf3ConnectionStateTb4b4b4.Te6edf3DeviceSleep Tff7b72-Tff7b72> Ta5d6ff"Ta5d6ffDEVICE_SLEEPTa5d6ff"
Tb4b4b4}
Served by rngit 1.5.0 - Generated in 0.17s